package com.xcity.game.activity.luckdraw;

import java.security.SecureRandom;
import java.text.MessageFormat;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.alibaba.fastjson.serializer.JSONSerializer;
import com.alibaba.fastjson.serializer.PropertyPreFilter;
import com.alibaba.fastjson.serializer.SerializeFilter;
import com.xcity.Server;
import com.xcity.db.entity.Player;
import com.xcity.game.activity.ActivityService;
import com.xcity.game.activity2.ActivityIds;
import com.xcity.game.chat.ChatService;
import com.xcity.game.common.Cause;
import com.xcity.game.common.MessageConst;
import com.xcity.game.common.PlayerPool;
import com.xcity.game.event.GameEvents;
import com.xcity.game.item.effect.AddCharmEffect.CharmDeltaType;
import com.xcity.pomelo.PomeloRoute;
import com.xcity.pomelo.push.NoticePush;
import com.xcity.pomelo.push.msg.ActivityStatePush;
import com.xcity.pomelo.request.ActivityBigLuckDrawListRequest;
import com.xcity.pomelo.request.ActivityBigLuckDrawRecordsRequest;
import com.xcity.pomelo.request.ActivityBigLuckDrawRequest;
import com.xcity.pomelo.response.ActivityBigLuckDrawListResponse;
import com.xcity.pomelo.response.ActivityBigLuckDrawRecordsResponse;
import com.xcity.pomelo.response.ActivityBigLuckDrawResponse;
import com.xcity.pomelo.response.HintResponse;

import gnu.trove.map.TIntIntMap;
import gnu.trove.map.hash.TIntIntHashMap;
import naga.x.App;
import naga.x.event.Event;
import naga.x.event.EventListener;
import naga.x.game.template.TemplateService;
import naga.x.game.time.TimeUpdater;
import naga.x.net.Session;
import naga.x.net.annotation.OPHandler;
import naga.x.net.impl.pomelo.annotation.PomeloOP;
import naga.x.service.ServiceException;
import naga.x.util.CommonUtils;
import naga.x.util.TimeUtils;

/**
 * 超级大转盘活动<br/>
 * 玩家每消耗一只玫瑰花获得6点积分，<br/>
 * 每消耗一捧99只玫瑰花获得600积分，<br/>
 * 每给一个玩家点赞可以获得2点积分。<br/>
 * 积分可以用于大转盘抽奖活动，每6个积分可以抽奖一次。<br/>
 * @author yang.li
 *
 */
@OPHandler
public class BigLuckDrawActivityService extends ActivityService implements EventListener {

	protected long beginTime = TimeUtils.parse("2016-11-08 00:00:00").getTime();
	protected long endTime = TimeUtils.parse("3016-12-01 00:00:00").getTime(); // 长期
	protected List<BigLuckDrawTemplate> templates;
	
	protected static final int POINTS_ONE_ROSE = 6;
	protected static final int POINTS_99_ROSE = 600;
	protected static final int POINTS_TAP_LIKE = 2;
	protected static final int DRAW_COST_POINTS = 6;
	protected static final int MAX_RECORDS_SIZE = 20;
	protected static final Random RND = new SecureRandom();
	protected static final int OPEN_LEVEL = 21;
	
	// 中奖纪录序列化过滤器
	static final SerializeFilter LOTTERY_RECORD_SERIALIZE_FILTER = new PropertyPreFilter() {
		@Override
		public boolean apply(JSONSerializer serializer, Object object, String name) {
			// 只序列化id、name、count
			return "id".equals(name) || "time".equals(name) || "count".equals(name);
		}
	};
	
	static enum PRIZE {
		TOP("特等奖"), // 特等奖
		FIRST("一等奖"), // 一等奖
		SECOND("二等奖"), // 二等奖
		THIRD("三等奖"), // 三等奖
		;
		public final String name;
		PRIZE(String name) {
			this.name = name;
		}
		static PRIZE of(int index) {
			PRIZE[] vals = values();
			return index >= 0 && index < vals.length ? vals[index] : null;
		}
	}
	
	@Override
	public void startup() throws ServiceException {
		templates = App.getApp().getServiceManager().get(TemplateService.class).getTemplatesList(BigLuckDrawTemplate.class);
		Collections.sort(templates);
		// 重新排序(概率越大越靠后)后需再次计算权重
		BigLuckDrawTemplate.maxProbability = 0;
		for (int i = 0, size = templates.size(); i < size; i++) {
			templates.get(i).validate();
		}
		if (!isEnd()) {
			App.getApp().getEventManager().register(this);
		}
//		test();
	}
	
	private void test() {
		for (int i = 0, size = templates.size(); i < size; i++) {
			BigLuckDrawTemplate template = templates.get(i);
			PRIZE p = PRIZE.of(i);
			System.out.println((p != null ? p.name : i) + ": [" + template.getMinVal() + "~" + template.getMaxVal() + ")");
		}
		int rounds = 10000000;
		TIntIntMap stat = new TIntIntHashMap(8);
		for (int i = 0; i < rounds; i++) {
			int n = RND.nextInt(BigLuckDrawTemplate.maxProbability);
			int index = CommonUtils.indexOfIntervals(templates, n);
			stat.put(index, stat.get(index) + 1);
			PRIZE p = PRIZE.of(index);
			if (p != null) {
//				System.out.println(p.name + " 第" + (i + 1) + "轮");
			}
		}
		System.out.println(BigLuckDrawTemplate.maxProbability);
		for (int i = 0; i < templates.size(); i++) {
			int n = stat.get(i);
			PRIZE p = PRIZE.of(i);
			System.out.print(p != null ? p.name : i);
			System.out.print(": 理论中奖概率=");
			System.out.print(templates.get(i).getProbability() * 100f / BigLuckDrawTemplate.maxProbability);
			System.out.print("% , 实际中奖=");
			System.out.print(n);
			System.out.print(", ");
			System.out.println((n * 100f / rounds) + "%.");
		}
		System.exit(0);
	}

	@Override
	public void shutdown() throws ServiceException {
	}
	
	@PomeloOP(PomeloRoute.ACTIVITY_BIG_LUCK_DRAW_LIST)
	protected void requestList(Session session, ActivityBigLuckDrawListRequest req) {
		Player player = session.getClient();
		session.send(new ActivityBigLuckDrawListResponse(req.getSerial(),
														player.getPool().getIntValue(PlayerPool.PROPERTY_ACTIVITY_BIG_LUCK_DRAW_POINTS),
														templates));
	}
	
	@PomeloOP(PomeloRoute.ACTIVITY_BIG_LUCK_DRAW)
	public void requestDraw(Session session, ActivityBigLuckDrawRequest req) {
		if (isEnd()) {
			session.send(new HintResponse(req.getSerial(), MessageConst.ACTIVITY_NOT_BEGIN));
			return;
		}
		Player player = session.getClient();
		int points = player.getPool().getIntValue(PlayerPool.PROPERTY_ACTIVITY_BIG_LUCK_DRAW_POINTS);
		if (points < DRAW_COST_POINTS) {
			session.send(new HintResponse(req.getSerial(), MessageConst.ACTIVITY_BIG_LUCK_DRAW_POINTS_NOT_ENOUGH));
			return;
		}
		int newPoints = subtractPoint(player, DRAW_COST_POINTS, Cause.ACTIVITY_LUCKDRAW);//player.poolSubtract(PlayerPool.PROPERTY_ACTIVITY_BIG_LUCK_DRAW_POINTS, DRAW_COST_POINTS);
		int n = RND.nextInt(BigLuckDrawTemplate.maxProbability);
		int index = CommonUtils.indexOfIntervals(templates, n);
		BigLuckDrawTemplate template = index >= 0 && index < templates.size() ? templates.get(index) : null;
		if (template != null && template.getId() > 0) {
			if (template.getId() == BigLuckDrawTemplate.ID_DOUBLE_POINTS) {
				newPoints = addPoint(player, template.getCount(), 1, Cause.ACTIVITY_LUCKDRAW);
			} else {
				player.getBag().addItemWithoutException(template.getId(), template.getCount(), Cause.ACTIVITY_LUCKDRAW);
			}
			// 纪录
			addRecord(player, template);
			// 公告
			PRIZE prize = PRIZE.of(index);
			if (prize != null) {
				bigPrizeNotice(player, prize, template);
			}
		}
		session.send(new ActivityBigLuckDrawResponse(req.getSerial(), index, newPoints));
		LOG.info("[ACTLUCKDRAW]ID[{}]OLD[{}]NEW[{}]WEIGHT[{}]INDEX[{}]ITEM[{}]COUNT[{}]",
							player.getId(), points, newPoints,
							n, index, template != null ? template.getId() : 0, template != null ? template.getCount() : 0);
	}
	
	@PomeloOP(PomeloRoute.ACTIVITY_BIG_LUCK_DRAW_RECORD)
	protected void requestLotteryRecords(Session session, ActivityBigLuckDrawRecordsRequest req) {
		Player player = session.getClient();
		List<BigLuckDrawLotteryRecord> records = player.getPool().getObject(PlayerPool.PROPERTY_ACTIVITY_BIG_LUCK_DRAW_RECORDS, type);
		session.send(new ActivityBigLuckDrawRecordsResponse(req.getSerial(), records));
	}
	
	protected boolean isEnd() {
		long now = TimeUpdater.getInstance().now();
		return now < beginTime || now >= endTime;
	}

	@Override
	public int[] getEventTypes() {
		return new int[] {
				GameEvents.EVENT_PLAYER_LOGIN, 
				GameEvents.EVENT_PLAYER_LOGOUT, 
				GameEvents.EVENT_PLAYER_LOADED,
				GameEvents.EVENT_PRESENT_ROSE, 
				GameEvents.EVENT_PLAYER_LEVEL_UP,
				GameEvents.EVENT_TAP_LIKE
				};
	}

	@Override
	public void handleEvent(Event event) {
		switch (event.getType()) {
			case GameEvents.EVENT_PLAYER_LOGIN:
				break;
			case GameEvents.EVENT_PLAYER_LOADED:
				playerLoaded(event.getParameter(0));
				break;
			case GameEvents.EVENT_PLAYER_LOGOUT:
				playerLogout(event.getParameter());
				break;
			case GameEvents.EVENT_PRESENT_ROSE:
				if (!isEnd()) {
					presentRose(event.getParameter(0), event.getParameter(2), event.getParameter(3));
				}
				break;
			case GameEvents.EVENT_TAP_LIKE:
				if (!isEnd()) {
					tapLike(event.getParameter(0));
				}
				break;
			case GameEvents.EVENT_PLAYER_LEVEL_UP:
				playerLvlUp(event.getParameter(0), event.getParameter(1));
				break;
		}
	}
	
	public void playerLoaded(Player player){
		if(player.getLevel() >= OPEN_LEVEL){
			player.send(new ActivityStatePush(ActivityIds.ACTIVITY_BIGLUCK, STATE_OPENED));
		}
	}
	public void playerLvlUp(Player player, int oldLvl){
		if(player.getLevel() >= OPEN_LEVEL && oldLvl < OPEN_LEVEL){
			player.send(new ActivityStatePush(ActivityIds.ACTIVITY_BIGLUCK, STATE_OPENED));
		}
	}

	private void presentRose(Player player, int roseId, int count) {
		if (roseId == CharmDeltaType.ROSE.itemId) {
			addPoint(player, POINTS_ONE_ROSE, count, Cause.PRESENT_ROSE);
		} else if (roseId == CharmDeltaType.ROSE99.itemId) {
			addPoint(player, POINTS_99_ROSE, count, Cause.PRESENT_ROSE99);
		}
	}
	
	private void tapLike(Player player) {
		addPoint(player, POINTS_TAP_LIKE, 1, Cause.TAP_LIKE);
	}
	
	private int addPoint(Player player, int point, int count, String cause) {
		int oldVal = player.getPool().getIntValue(PlayerPool.PROPERTY_ACTIVITY_BIG_LUCK_DRAW_POINTS);
		int newVal = player.poolAdd(PlayerPool.PROPERTY_ACTIVITY_BIG_LUCK_DRAW_POINTS, point * count);
		LOG.info("[ACTLUCKDRAW ADDPOINTS]ID[{}]OLD[{}]NEW[{}]P[{}]N[{}]CAUSE[{}]", player.getId(), oldVal, newVal, point, count, cause);
		return newVal;
	}
	
	private int subtractPoint(Player player, int point, String cause) {
		int oldVal = player.getPool().getIntValue(PlayerPool.PROPERTY_ACTIVITY_BIG_LUCK_DRAW_POINTS);
		int newVal = player.poolSubtract(PlayerPool.PROPERTY_ACTIVITY_BIG_LUCK_DRAW_POINTS, point);
		LOG.info("[ACTLUCKDRAW DECPOINTS]ID[{}]OLD[{}]NEW[{}]P[{}]CAUSE[{}]", player.getId(), oldVal, newVal, point, cause);
		return newVal;
	}
	
	private void addRecord(Player player, BigLuckDrawTemplate item) {
		LinkedList<BigLuckDrawLotteryRecord> records = player.getPool().getObject(PlayerPool.PROPERTY_ACTIVITY_BIG_LUCK_DRAW_RECORDS, type);
		if (records == null) {
			records = new LinkedList<BigLuckDrawLotteryRecord>();
			player.poolSet(PlayerPool.PROPERTY_ACTIVITY_BIG_LUCK_DRAW_RECORDS, records);
		}
		if (records.size() > MAX_RECORDS_SIZE) {
			records.pollLast();
		}
		records.addFirst(new BigLuckDrawLotteryRecord(item));
	}
	
	static TypeReference<LinkedList<BigLuckDrawLotteryRecord>> type = new TypeReference<LinkedList<BigLuckDrawLotteryRecord>>() {};
	private void playerLogout(Player player) {
		LinkedList<BigLuckDrawLotteryRecord> records = player.getPool().getObject(PlayerPool.PROPERTY_ACTIVITY_BIG_LUCK_DRAW_RECORDS, type);
		if (records != null) {
			String jsonStr = JSON.toJSONString(records, LOTTERY_RECORD_SERIALIZE_FILTER);
			player.poolSet(PlayerPool.PROPERTY_ACTIVITY_BIG_LUCK_DRAW_RECORDS, jsonStr);
		}
	}
	
	/** 大奖公告 */
	private void bigPrizeNotice(Player player, PRIZE prize, BigLuckDrawTemplate template) {
		String notice = MessageFormat.format(MessageConst.ACTIVITY_BIG_LUCK_DRAW_BIG_PRIZE_NOTICE, player.getName(), prize.name, template.getName(), template.getCount());
		Server.broadcast(new NoticePush(NoticePush.NOTICE_MARQUEE, notice));
		App.getApp().getServiceManager().get(ChatService.class).sendSystemChat(notice);
	}

}
